// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/remoting/metrics.h"

#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "media/audio/sample_rates.h"

namespace media {
namespace remoting {

    namespace {

        ////////////////////////////////////////////////////////////////////////////////
        // BEGIN: These were all borrowed from src/media/filters/ffmpeg_demuxer.cc.
        // TODO(miu): This code will be de-duped in a soon-upcoming change.

        // Some videos just want to watch the world burn, with a height of 0; cap the
        // "infinite" aspect ratio resulting.
        constexpr int kInfiniteRatio = 99999;

        // Common aspect ratios (multiplied by 100 and truncated) used for histogramming
        // video sizes.  These were taken on 20111103 from
        // http://wikipedia.org/wiki/Aspect_ratio_(image)#Previous_and_currently_used_aspect_ratios
        constexpr int kCommonAspectRatios100[] = {
            100,
            115,
            133,
            137,
            143,
            150,
            155,
            160,
            166,
            175,
            177,
            185,
            200,
            210,
            220,
            221,
            235,
            237,
            240,
            255,
            259,
            266,
            276,
            293,
            400,
            1200,
            kInfiniteRatio,
        };

        // END: Code borrowed from src/media/filter/ffmpeg_demuxer.cc.
        ////////////////////////////////////////////////////////////////////////////////

        // Buckets for video width histograms.
        constexpr int kVideoWidthBuckets[] = {
            180,
            240,
            320,
            480,
            640,
            720,
            872,
            940,
            1280,
            1440,
            1600,
            1760,
            1920,
            2560,
            3840,
            7680,
            16384,
        };

    } // namespace

    SessionMetricsRecorder::SessionMetricsRecorder()
        : last_audio_codec_(kUnknownAudioCodec)
        , last_channel_layout_(CHANNEL_LAYOUT_NONE)
        , last_sample_rate_(0)
        , last_video_codec_(kUnknownVideoCodec)
        , last_video_profile_(VIDEO_CODEC_PROFILE_UNKNOWN)
        , remote_playback_is_disabled_(false)
    {
    }

    SessionMetricsRecorder::~SessionMetricsRecorder() = default;

    void SessionMetricsRecorder::WillStartSession(StartTrigger trigger)
    {
        DCHECK(!start_trigger_);
        start_trigger_ = trigger;
        start_time_ = base::TimeTicks::Now();
    }

    void SessionMetricsRecorder::DidStartSession()
    {
        UMA_HISTOGRAM_ENUMERATION("Media.Remoting.SessionStartTrigger",
            *start_trigger_, START_TRIGGER_MAX + 1);
        if (last_audio_codec_ != kUnknownAudioCodec)
            RecordAudioConfiguration();
        if (last_video_codec_ != kUnknownVideoCodec)
            RecordVideoConfiguration();
        RecordTrackConfiguration();
    }

    void SessionMetricsRecorder::WillStopSession(StopTrigger trigger)
    {
        if (!start_trigger_)
            return;

        // Record what triggered the end of the session.
        UMA_HISTOGRAM_ENUMERATION("Media.Remoting.SessionStopTrigger", trigger,
            STOP_TRIGGER_MAX + 1);

        // Record the session duration.
        const base::TimeDelta session_duration = base::TimeTicks::Now() - start_time_;
        UMA_HISTOGRAM_CUSTOM_TIMES("Media.Remoting.SessionDuration", session_duration,
            base::TimeDelta::FromSeconds(15),
            base::TimeDelta::FromHours(12), 50);

        // Reset |start_trigger_| since metrics recording of the current remoting
        // session has now completed.
        start_trigger_ = base::nullopt;
    }

    void SessionMetricsRecorder::OnPipelineMetadataChanged(
        const PipelineMetadata& metadata)
    {
        if (metadata.has_audio && metadata.audio_decoder_config.IsValidConfig()) {
            const auto& config = metadata.audio_decoder_config;
            // While in a remoting session, record audio configuration changes.
            const bool need_to_record_audio_configuration = start_trigger_ && (config.codec() != last_audio_codec_ || config.channel_layout() != last_channel_layout_ || config.samples_per_second() != last_sample_rate_);
            last_audio_codec_ = config.codec();
            last_channel_layout_ = config.channel_layout();
            last_sample_rate_ = config.samples_per_second();
            if (need_to_record_audio_configuration)
                RecordAudioConfiguration();
        } else {
            last_audio_codec_ = kUnknownAudioCodec;
            last_channel_layout_ = CHANNEL_LAYOUT_NONE;
            last_sample_rate_ = 0;
        }

        if (metadata.has_video && metadata.video_decoder_config.IsValidConfig()) {
            const auto& config = metadata.video_decoder_config;
            // While in a remoting session, record video configuration changes.
            const bool need_to_record_video_configuration = start_trigger_ && (config.codec() != last_video_codec_ || config.profile() != last_video_profile_ || metadata.natural_size != last_natural_size_);
            last_video_codec_ = config.codec();
            last_video_profile_ = config.profile();
            last_natural_size_ = metadata.natural_size;
            if (need_to_record_video_configuration)
                RecordVideoConfiguration();
        } else {
            last_video_codec_ = kUnknownVideoCodec;
            last_video_profile_ = VIDEO_CODEC_PROFILE_UNKNOWN;
            last_natural_size_ = gfx::Size();
        }

        // While in a remoting session, record whether audio or video media streams
        // started or ended.
        if (start_trigger_)
            RecordTrackConfiguration();
    }

    void SessionMetricsRecorder::OnRemotePlaybackDisabled(bool disabled)
    {
        if (disabled == remote_playback_is_disabled_)
            return; // De-dupe redundant notifications.
        UMA_HISTOGRAM_BOOLEAN("Media.Remoting.AllowedByPage", !disabled);
        remote_playback_is_disabled_ = disabled;
    }

    void SessionMetricsRecorder::OnPosterImageDownloaded(
        base::TimeDelta download_duration,
        bool success)
    {
        const std::string name = success
            ? "Media.Remoting.PosterDownloadDuration.Success"
            : "Media.Remoting.PosterDownloadDuration.Fail";
        // Note: Not using UMA_HISTOGRAM_CUSTOM_TIMES because |name| is a variable in
        // in this instance; and so the "one histogram" static local should not be
        // created.
        base::UmaHistogramCustomTimes(name, download_duration,
            base::TimeDelta::FromMilliseconds(10),
            base::TimeDelta::FromSeconds(30), 50);
    }

    void SessionMetricsRecorder::RecordAudioConfiguration()
    {
        UMA_HISTOGRAM_ENUMERATION("Media.Remoting.AudioCodec", last_audio_codec_,
            kAudioCodecMax + 1);
        UMA_HISTOGRAM_ENUMERATION("Media.Remoting.AudioChannelLayout",
            last_channel_layout_, CHANNEL_LAYOUT_MAX + 1);
        AudioSampleRate asr;
        if (ToAudioSampleRate(last_sample_rate_, &asr)) {
            UMA_HISTOGRAM_ENUMERATION("Media.Remoting.AudioSamplesPerSecond", asr,
                kAudioSampleRateMax + 1);
        } else {
            UMA_HISTOGRAM_COUNTS_1M("Media.Remoting.AudioSamplesPerSecondUnexpected",
                last_sample_rate_);
        }
    }

    void SessionMetricsRecorder::RecordVideoConfiguration()
    {
        UMA_HISTOGRAM_ENUMERATION("Media.Remoting.VideoCodec", last_video_codec_,
            kVideoCodecMax + 1);
        UMA_HISTOGRAM_ENUMERATION("Media.Remoting.VideoCodecProfile",
            last_video_profile_, VIDEO_CODEC_PROFILE_MAX + 1);
        UMA_HISTOGRAM_CUSTOM_ENUMERATION(
            "Media.Remoting.VideoNaturalWidth", last_natural_size_.width(),
            base::CustomHistogram::ArrayToCustomRanges(
                kVideoWidthBuckets, arraysize(kVideoWidthBuckets)));
        // Intentionally use integer division to truncate the result.
        const int aspect_ratio_100 = last_natural_size_.height()
            ? (last_natural_size_.width() * 100) / last_natural_size_.height()
            : kInfiniteRatio;
        UMA_HISTOGRAM_CUSTOM_ENUMERATION(
            "Media.Remoting.VideoAspectRatio", aspect_ratio_100,
            base::CustomHistogram::ArrayToCustomRanges(
                kCommonAspectRatios100, arraysize(kCommonAspectRatios100)));
    }

    void SessionMetricsRecorder::RecordTrackConfiguration()
    {
        TrackConfiguration config = NEITHER_AUDIO_NOR_VIDEO;
        if (last_audio_codec_ != kUnknownAudioCodec)
            config = AUDIO_ONLY;
        if (last_video_codec_ != kUnknownVideoCodec) {
            if (config == AUDIO_ONLY)
                config = AUDIO_AND_VIDEO;
            else
                config = VIDEO_ONLY;
        }
        UMA_HISTOGRAM_ENUMERATION("Media.Remoting.TrackConfiguration", config,
            TRACK_CONFIGURATION_MAX + 1);
    }

    RendererMetricsRecorder::RendererMetricsRecorder()
        : start_time_(base::TimeTicks::Now())
        , did_record_first_playout_(false)
    {
    }

    RendererMetricsRecorder::~RendererMetricsRecorder() = default;

    void RendererMetricsRecorder::OnRendererInitialized()
    {
        const base::TimeDelta elapsed_since_start = base::TimeTicks::Now() - start_time_;
        UMA_HISTOGRAM_CUSTOM_TIMES("Media.Remoting.TimeUntilRemoteInitialized",
            elapsed_since_start,
            base::TimeDelta::FromMilliseconds(10),
            base::TimeDelta::FromSeconds(30), 50);
    }

    void RendererMetricsRecorder::OnEvidenceOfPlayoutAtReceiver()
    {
        if (did_record_first_playout_)
            return;
        const base::TimeDelta elapsed_since_start = base::TimeTicks::Now() - start_time_;
        UMA_HISTOGRAM_CUSTOM_TIMES("Media.Remoting.TimeUntilFirstPlayout",
            elapsed_since_start,
            base::TimeDelta::FromMilliseconds(10),
            base::TimeDelta::FromSeconds(30), 50);
        did_record_first_playout_ = true;
    }

    void RendererMetricsRecorder::OnAudioRateEstimate(int kilobits_per_second)
    {
        UMA_HISTOGRAM_CUSTOM_COUNTS("Media.Remoting.AudioBitrate",
            kilobits_per_second, 1, 1024, 50);
    }

    void RendererMetricsRecorder::OnVideoRateEstimate(int kilobits_per_second)
    {
        UMA_HISTOGRAM_CUSTOM_COUNTS("Media.Remoting.VideoBitrate",
            kilobits_per_second, 1, 16 * 1024, 50);
    }

} // namespace remoting
} // namespace media
